package com.yuyuan.gateway.filters;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.yuyuan.api.client.UserClient;
import com.yuyuan.api.domain.UserDTOC;
import com.yuyuan.gateway.config.AuthProperties;
import com.yuyuan.common.domain.dto.UserDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

import static com.yuyuan.common.utils.RedisConstants.LOGIN_USER_TOKEN_KEY;

@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    @Autowired
    private AuthProperties authProperties;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Lazy
    @Autowired
    private UserClient userClient;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取request
        ServerHttpRequest request = exchange.getRequest();
        // 2.判断是否需要做登录拦截
        if(isExclude(request.getPath().toString())){
            // 放行
            return chain.filter(exchange);
        }
        // 3.获取token
        String token;
        List<String> headers = request.getHeaders().get("authorization");
        if (headers != null && !headers.isEmpty()) {
            token = headers.get(0);
        } else {
            throw new RuntimeException("请重新登录！");
        }

        // 4.校验并解析token
        String userId;
        ServerWebExchange swe;
        try {
            userId = JWT.decode(token).getKeyId();
            if (!stringRedisTemplate.opsForHash().entries(LOGIN_USER_TOKEN_KEY + userId).isEmpty()){
                Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(LOGIN_USER_TOKEN_KEY + userId);
                //验证密码
                UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
                checkTokenPassword(userDTO.getPassword(),token);
                //浮动有效期
                Random random = new Random();
                stringRedisTemplate.expire(LOGIN_USER_TOKEN_KEY+token,10060 + random.nextInt(20), TimeUnit.MINUTES);
                // 5.传递用户信息
                swe = saveUserDTOInHeader(userDTO, exchange);
            } else {
                UserDTOC userDTOC = userClient.selectById(Long.valueOf(userId));
                if (userDTOC == null) {
                    throw new RuntimeException("用户不存在，请重新登录");
                }
                UserDTO userDTO = new UserDTO(userDTOC.getId(), userDTOC.getName(), userDTOC.getIcon(), userDTOC.getPassword());
                //验证密码
                checkTokenPassword(userDTO.getPassword(),token);

                Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                        CopyOptions.create().setIgnoreNullValue(true)
                                .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));

                Random random = new Random();
                stringRedisTemplate.opsForHash().putAll(LOGIN_USER_TOKEN_KEY + userId, userMap);
                stringRedisTemplate.expire(LOGIN_USER_TOKEN_KEY + userId,10060 + random.nextInt(20),TimeUnit.MINUTES);
                // 5.传递用户信息
                swe = saveUserDTOInHeader(userDTO, exchange);
            }
        } catch (JWTDecodeException j) {
            throw new RuntimeException("401");
        }
        // 6.放行
        return chain.filter(swe);
    }

    private boolean isExclude(String path) {
        for (String pathPattern : authProperties.getExcludePaths()) {
            if (antPathMatcher.match(pathPattern, path)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        return 0;
    }

    public void checkTokenPassword(String password, String token){
        //验证密码
        JWTVerifier jwtverifier = JWT.require(Algorithm.HMAC256(password)).build();
        try{
            jwtverifier.verify(token);
        }
        catch (JWTVerificationException e){
            throw new RuntimeException("401");
        }
    }
    public ServerWebExchange saveUserDTOInHeader(UserDTO userDTO, ServerWebExchange exchange){
        String userDTOStringJSON = userDTO.toStringJSON();
        String encodedUserDTOStringJSON;
        try {
            encodedUserDTOStringJSON = URLEncoder.encode(userDTOStringJSON, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        return exchange.mutate()
                .request(builder -> builder.header("user-info", encodedUserDTOStringJSON))
                .build();
    }
}
